/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.html.model;

import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import offset.nodes.Constants;
import offset.nodes.client.editor.model.ComponentModel;
import offset.nodes.client.editor.model.HyperlinkModel;
import offset.nodes.client.model.HttpUtils;
import offset.nodes.client.virtual.model.jcr.QName;
import offset.nodes.server.model.RepositoryUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HtmlSubstituter {
    static Pattern componentPattern = Pattern.compile("<div " + ComponentModel.ATT_COMPONENT_ID + "=\"(.*?)\">.*?</div>", Pattern.DOTALL);
    static Logger logger = LoggerFactory.getLogger(HtmlSubstituter.class);
    
    Session session;
    String host;
    int port;

    public HtmlSubstituter(Session session) {
        this.session = session;
    }

    class InternalHyperlink {

        String path;
        String reference;
        String propertyName;

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getPropertyName() {
            return propertyName;
        }

        public void setPropertyName(String propertyName) {
            this.propertyName = propertyName;
        }

        public String getReference() {
            return reference;
        }

        public void setReference(String reference) {
            this.reference = reference;
        }
    }

    /**
     * Parse the internal hyperlink into its components
     * 
     * @param hyperlink
     * @return
     * @throws MalformedURLException
     */
    protected InternalHyperlink parseInternalHyperlink(String hyperlink) {
        InternalHyperlink result = new InternalHyperlink();

        final String hrefKey = "href=\"";
        int startHref = hyperlink.indexOf(hrefKey);
        int endHref = hyperlink.indexOf("\"", startHref + hrefKey.length());
        String href = hyperlink.substring(startHref + hrefKey.length(), endHref);

        int startPath = href.indexOf(Constants.PATH_SEPARATOR, href.indexOf("//") + 2);
        int endPath = href.indexOf("?");

        result.setPath(href.substring(startPath, endPath));

        HashMap<String, String> parameters = HttpUtils.getParameters(href);
        result.setReference(parameters.get(Constants.PAR_NODE_REFERENCE));
        result.setPropertyName(parameters.get(Constants.PAR_PROPERTY_NAME));

        return result;
    }

    /**
     * Find the node either based on path or on reference value
     * 
     * @param hyperlink
     * @return
     * @throws RepositoryException
     */
    protected Node findNode(InternalHyperlink hyperlink) throws RepositoryException {
        try {
            if (hyperlink.getReference() != null)
                return session.getNodeByUUID(hyperlink.getReference());
        } catch (Throwable e) {
            logger.warn("node not found: " + hyperlink.getReference());
            return null;
        }

        if (session.getRootNode().hasNode(hyperlink.getPath()))
            return session.getRootNode().getNode(hyperlink.getPath());

        return null;
    }

    /**
     * Create a local hyperlink including the node information
     * 
     * @param hyperlink
     * @param node
     * @return
     * @throws RepositoryException
     */
    public String createHyperlink(InternalHyperlink hyperlink, Node node) throws RepositoryException {
        String result = "<a href=\"" + hyperlink.getPath();
        String propertyValue;

        if (hyperlink.getPropertyName().equals(QName.NAME_PROPERTY.getLocalName()))
            propertyValue = node.getName();
        else
            propertyValue = node.getProperty(hyperlink.getPropertyName()).getString();

        if (hyperlink.getReference() != null)
            return HttpUtils.addParameter(result, Constants.PAR_NODE_REFERENCE, hyperlink.getReference())
                    + "\">" + propertyValue;
        else
            return result
                    + "\">";
    }

    /**
     * Replace all expression(s) in input by replacement.
     * Don't interpret expression as a regular expression.
     * 
     * @param input
     * @param expression
     * @param replacement
     * @return
     */
    public String replaceAll(String input, String expression, String replacement) {
        int start = input.indexOf(expression);
        if (start < 0)
            return input;

        StringBuilder buf = new StringBuilder();
        int unprocessed = 0;
        while (start >= 0) {
            buf.append(input.substring(unprocessed, start));
            buf.append(replacement);
            unprocessed = start + expression.length();
            start = input.indexOf(expression, unprocessed);
        }
        buf.append(input.substring(unprocessed));

        return buf.toString();
    }

    /**
     * Substitute the internal hyperlinks.
     *
     * Substitute
     * 1. The repository URL ({$repositoryUrl} > current repository URL. This allows to change the repository URL. Do this as well in images.
     * 2. Property hyperlinks of the form "<repositoryUrl>?reference=<node reference>&propertyName=<name of the property>".
     * For such hyperlinks the propertyName parameter will be used to determine the property Value in conjunction with the node reference.
     * Then the propertyName parameter will be removed and the body of the hyperlink will be set to the property Value.
     * 
     * @param div
     * @return
     * @throws MalformedURLException
     * @throws RepositoryException
     */
    public String substituteHyperlinks(String div) throws RepositoryException {
        div = replaceAll(div, HyperlinkModel.REPOSITORY_URL, RepositoryUtils.getRepositoryURL());

        final String hyperlinkBegin = "<a";
        final String hyperlinkEnd = "</a>";
        String[] hyperlinks = div.split(hyperlinkBegin);

        StringBuffer result = new StringBuffer();
        for (String hyperlinkFragment : hyperlinks) {
            int endHyperlink = hyperlinkFragment.indexOf(hyperlinkEnd);
            if (endHyperlink < 0) {
                result.append(hyperlinkFragment);
                continue;
            }

            String hyperlink = hyperlinkFragment.substring(0, endHyperlink);

            int namePropertyStart = hyperlink.indexOf(Constants.PAR_PROPERTY_NAME + "=");
            if (namePropertyStart < 0) { // no substitution necessary
                result.append(hyperlinkBegin + hyperlinkFragment);
                continue;
            }

            InternalHyperlink internalHyperlink = parseInternalHyperlink(hyperlink);
            Node node = findNode(internalHyperlink);
            if (node == null)
                continue;

            String newHyperlink = createHyperlink(internalHyperlink, node);

            result.append(newHyperlink);
            result.append(hyperlinkFragment.substring(endHyperlink));
        }
        return result.toString();
    }
    
    /**
     * Substitute the component reference by the content of the component page.
     * 
     * @param div the page content with the references
     * @param context information to retrieve the component page
     * @return the substituted content
     * @throws Exception
     */
    public String substituteComponentPage(String div, HttpContext context) throws Exception {
        if (div.indexOf(ComponentModel.ATT_COMPONENT_ID) < 0)
            return div;
        
        PageModel model = new PageModel(session);
        
        Matcher matcher = componentPattern.matcher(div);
        StringBuffer buf = new StringBuffer();
        
        while(matcher.find()) {
            String uuid = matcher.group(1);
            
            String component = model.getPageContent(uuid, context);
            matcher.appendReplacement(buf, component);
        }        
        matcher.appendTail(buf);
        
        return buf.toString();        
    }

    public static void main(String[] arguments) throws Exception {
        String input = "lksdflijsd <a href=\"http://localhost:9094/a/b/c?reference=xxx&propertyName=NAME\">xxx</a> lksdflkjsd </a href=\"/a/b?reference=xx\">lksdf</a> lksdf</p>\n" +
"                        <div n-component-id=\"489bf407-ae06-440d-ad09-ab5d6d4ff524\">\n" +
"                          \n" +
"                        </div>\n" +
"                        <p>\n" +
"                          repository. lksjdflkjsdf";
        HtmlSubstituter transformer = new HtmlSubstituter(null);

        System.out.println(transformer.substituteComponentPage(input, null));

    }
}
